;; =============
;; PREVIOUS CODE
;; =============

(define (make-segment point1 point2)
  (cons point1 point2))

(define (start-segment seg)
  (car seg))

(define (end-segment seg)
  (cdr seg))


(define (make-point xcoord ycoord)
  (cons xcoord ycoord))

(define (x-point point)
  (car point))

(define (y-point point)
  (cdr point))


(define (average a b)
  (/ (+ a b) 2))

(define (midpoint-segment seg)
  (let ([point1 (start-segment seg)]
        [point2 (end-segment seg)])
    (make-point (average (x-point point1) (x-point point2))
                (average (y-point point1) (y-point point2)))))

(define* (print-point-testable p #:optional port)
  (display
   (simple-format #f "(~a,~a)\n"
                  (x-point p) (y-point p))
   port))
;; ============
;; Exercise 2.3
;; ============

;; Implement a representation for rectangles in a plane. (Hint: You
;; may want to make use of Exercise 2.2.)

;; In terms of your constructors and selectors, create procedures that
;; compute the perimeter and the area of a given rectangle.

;; Now implement a different representation for rectangles.

;; Can you design your system with suitable abstraction barriers, so
;; that the same perimeter and area procedures will work using either
;; representation?

;; ========
;; SOLUTION
;; ========

;; A: Implement a representation for rectangles in a plane. (Hint: You
;; may want to make use of Exercise 2.2.)

;; A rectangle may be represented by 2 points, one at the top left
;; corner and one at the bottom right corner.
(define (make-rectangle top-left bottom-right)
  (cons top-left bottom-right))

(define (top-left-corner rect)
  (car rect))

(define (bottom-right-corner rect)
  (cdr rect))

(define (top-right-corner rect)
  (let ([top-left (top-left-corner rect)]
        [bottom-right (bottom-right-corner rect)])
    (make-point (x-point bottom-right)
                (y-point top-left))))

(define (bottom-left-corner rect)
  (let ([top-left (top-left-corner rect)]
        [bottom-right (bottom-right-corner rect)])
    (make-point (x-point top-left)
                (y-point bottom-right))))

;; B: In terms of your constructors and selectors, create procedures that
;; compute the perimeter and the area of a given rectangle.
(define (vertical-distance point1 point2)
  (abs (- (y-point point1) (y-point point2))))

(define (horizontal-distance point1 point2)
  (abs (- (x-point point1) (x-point point2))))

(define (perimeter rect)
  (let ([top-left (top-left-corner rect)]
        [top-right (top-right-corner rect)]
        [bottom-left (bottom-left-corner rect)]
        [bottom-right (bottom-right-corner rect)])
    (+ (vertical-distance top-left bottom-left)
       (vertical-distance top-right bottom-right)
       (horizontal-distance top-left top-right)
       (horizontal-distance bottom-left bottom-right))))

(define (area rect)
  (let ([top-left (top-left-corner rect)]
        [top-right (top-right-corner rect)]
        [bottom-left (bottom-left-corner rect)]
        [bottom-right (bottom-right-corner rect)])
    (* (vertical-distance top-left bottom-left)
       (horizontal-distance top-left top-right))))

;; C: Now implement a different representation for rectangles.

;; A rectangle could also be defined by specifying the 4 corner points.
(define (make-rectangle-2 top-left top-right bottom-left bottom-right)
  (cons top-left
        (cons top-right
              (cons bottom-left
                    (cons bottom-right '())))))

(define (top-left-corner-2 rect)
  (car rect))

(define (top-right-corner-2 rect)
  (cadr rect))

(define (bottom-left-corner-2 rect)
  (caddr rect))

(define (bottom-right-corner-2 rect)
  (cadddr rect))

;; The rest remains unchanged except for names, to be able to have
;; this in one file. If this were code changes, they would have the
;; same names.
(define (vertical-distance point1 point2)
  (abs (- (y-point point1) (y-point point2))))

(define (horizontal-distance point1 point2)
  (abs (- (x-point point1) (x-point point2))))

(define (perimeter-2 rect)
  (let ([top-left (top-left-corner-2 rect)]
        [top-right (top-right-corner-2 rect)]
        [bottom-left (bottom-left-corner-2 rect)]
        [bottom-right (bottom-right-corner-2 rect)])
    (+ (vertical-distance top-left bottom-left)
       (vertical-distance top-right bottom-right)
       (horizontal-distance top-left top-right)
       (horizontal-distance bottom-left bottom-right))))

(define (area-2 rect)
  (let ([top-left (top-left-corner-2 rect)]
        [top-right (top-right-corner-2 rect)]
        [bottom-left (bottom-left-corner-2 rect)]
        [bottom-right (bottom-right-corner-2 rect)])
    (* (vertical-distance top-left bottom-left)
       (horizontal-distance top-left top-right))))

;; D: Can you design your system with suitable abstraction barriers, so
;; that the same perimeter and area procedures will work using either
;; representation?

;; =====
;; TESTS
;; =====
(use-modules (srfi srfi-64))

(test-begin "test-2.03")
(test-group
 "test-2.03"

 (test-eqv (perimeter-2 (make-rectangle-2 (make-point 1 1)
                                          (make-point 3 1)
                                          (make-point 1 3)
                                          (make-point 3 3)))
   (perimeter (make-rectangle (make-point 1 1)
                              (make-point 3 3))))

 (test-eqv (area-2 (make-rectangle-2 (make-point 1 1)
                                     (make-point 3 1)
                                     (make-point 1 3)
                                     (make-point 3 3)))
   (area (make-rectangle (make-point 1 1)
                         (make-point 3 3)))))
(test-end "test-2.03")
